﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml;
using DARE;
using DAREContentProcessor.Properties;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
using Microsoft.Xna.Framework.Graphics;

namespace ContentProcessor
{
    /// <summary>
    /// Animation Processor
    /// This Processor extract only the animation information for an FBX file
    /// </summary>
    [ContentProcessor(DisplayName = "Animation Extract - DARE")]
    public sealed class CAnimationProcessor : ContentProcessor<NodeContent, CAnimationContent>
    {
        #region Fields

        //private bool m_containeBone;
        //private CAnimation m_animation;
        private CAnimationContent m_animContent;
        private List<CAnimNode> m_nodes;
        private Dictionary<string, List<string>> m_nodesNameAffected;
        private ContentProcessorContext m_context;
        private AnimationContentDictionary m_animations = new AnimationContentDictionary();
        private ContentIdentity m_identity;


        #endregion

        #region Property

        ///// <summary>
        ///// Path of the file that describe how to split the current animation
        ///// </summary>
        //public string AnimationsDescriptionFilePath { get; set; }

        /// <summary>
        /// Property used to set the animation's name 
        /// </summary>
        public string AnimationName { get; set; }

        /// <summary>
        /// Property used to activate or desactivate the remove of useless key into the animation information
        /// </summary>
        public bool RemoveUselessKey { get; set; }

        #endregion

        #region CTOR

        /// <summary>
        /// Constructor
        /// </summary>
        public CAnimationProcessor()
        {
            RemoveUselessKey = true;
        }

        #endregion

        #region Method

        /// <summary>
        /// Method used to transfom input information into a CAnimation
        /// </summary>
        /// <param name="input">Model input</param>
        /// <param name="context">Build's context</param>
        /// <returns>CAnimation</returns>
        public override CAnimationContent Process(NodeContent input, ContentProcessorContext context)
        {
            m_context = context;
            m_identity = input.Identity;
            m_nodesNameAffected = new Dictionary<string, List<string>>();
            m_animContent = new CAnimationContent();

            FindAnimations(input);

            if (m_animations.Count <= 0)
                throw new InvalidDataException(Resources.EmptyAnimation);

            m_nodes = ReferenceBones(input);

            if (string.IsNullOrEmpty(AnimationName))
                AnimationName = m_animations.Keys.ElementAt(0);
            foreach (AnimationContent anim in m_animations.Values)
            {
                if (anim.Duration.Ticks < (1.0f / 60.0f))
                {
                    context.Logger.LogWarning("", anim.Identity, Resources.SmallTickError, "{", "}");
                    break;
                }
            }

            GenerateAnimationData(input);
            return m_animContent;
        }

        //// Using m_animation et recherche dans les mesh s'il y a des infos de skinning
        public void InfoSkinning(ModelContent model)
        {
            foreach (var elem in model.Meshes)
            {
                elem.Tag = false;
                MeshContent mc = elem.SourceMesh;
                foreach (var el in mc.Geometry)
                {
                    elem.Tag = el.Vertices.Channels.Contains(VertexChannelNames.EncodeName("BlendIndices", 0));
                    if ((bool)elem.Tag)
                        break;
                }
            }
        }

        /// <summary>
        /// Method used to extract bone information
        /// </summary>
        /// <param name="skeleton"></param>
        /// <returns></returns>
        private List<CAnimNode> ReferenceBones(NodeContent root)
        {
            List<NodeContent> bones = new List<NodeContent>();

            var anim = CModelAnimationProcessor.GenerateAnimNodes(root);
            return anim;
        }

        /// <summary>
        /// Searches through the NodeContent tree for all animations and puts them in
        /// one AnimationContentDictionary
        /// </summary>
        /// <param name="node">The root of the tree</param>
        private void FindAnimations(NodeContent node)
        {
            foreach (KeyValuePair<string, AnimationContent> k in node.Animations)
            {
                if (!m_nodesNameAffected.ContainsKey(k.Key))
                    m_nodesNameAffected.Add(k.Key, new List<string>());
                if (m_animations.ContainsKey(k.Key))
                {
                    foreach (KeyValuePair<string, AnimationChannel> c in k.Value.Channels)
                        m_animations[k.Key].Channels.Add(c.Key, c.Value);
                }
                else
                {
                    m_animations.Add(k.Key, k.Value);
                }
                m_nodesNameAffected[k.Key].AddRange(k.Value.Channels.Keys);
            }

            foreach (NodeContent child in node.Children)
                FindAnimations(child);
        }

        /// <summary>
        /// Generate animation's information from the AnimationContent
        /// </summary>
        /// <param name="_root">root of the tree node</param>
        private void GenerateAnimationData(NodeContent _root)
        {
            GenerateAnimationBank();
        }
        /// <summary>
        /// Method used to modelise animation data into a good format
        /// </summary>
        private void GenerateAnimationBank()
        {

            //double _start = TimeSpan.MaxValue.TotalMilliseconds;
            //double _end = TimeSpan.MinValue.TotalMilliseconds;

            //this.m_context.Logger.LogWarning("", this.m_identity, "-----------------------\n{0}\n {1}\n {2}", this.m_context.OutputFilename, this.m_context.OutputDirectory, this.m_context.OutputFilename.Replace(this.m_context.OutputDirectory, ""));

            var _path = this.m_context.OutputFilename.Replace(this.m_context.OutputDirectory, "").Replace('/', '-').Replace('\\', '-');
            _path = _path.Remove(_path.LastIndexOf(Path.GetExtension(_path)));
            //this.m_context.Logger.LogWarning("", this.m_identity, "{0}\n {1}\n {2}\n{3}", this.m_context.OutputFilename, this.m_context.OutputDirectory, this.m_context.OutputFilename.Replace(this.m_context.OutputDirectory, ""), _path);
            foreach (var el in m_animations)
            {
                string _animName = _path + ':' + ((m_animations.Count == 1) ? AnimationName : el.Key);
                CAnimation m_animation = new CAnimation(_animName, (from _node in m_nodes where m_nodesNameAffected[el.Key].Contains(_node.Name) select _node).ToArray());
                Dictionary<TimeSpan, Dictionary<KeyValuePair<int, string>, CTransform>> _frame = new Dictionary<TimeSpan, Dictionary<KeyValuePair<int, string>, CTransform>>();
                List<CAnimNode> _nodeAnimated = new List<CAnimNode>();

                foreach (var anim in el.Value.Channels)
                {
                    int nodeIndex = m_animation.GetNodeIndexByName(anim.Key);
                    Debug.Assert(nodeIndex > -1);

                    CAnimNode node = (from e in m_nodes
                                      where e.Name == anim.Key
                                      select e).SingleOrDefault();

                    //CAnimNode node = m_nodes[nodeIndex];
                    List<KeyValuePair<CTransform, CTransform>> _diff = new List<KeyValuePair<CTransform, CTransform>>();

                    foreach (var elem in anim.Value)
                    {
                        //if (_start > elem.Time.TotalMilliseconds)
                        //    _start = elem.Time.TotalMilliseconds;
                        //if (_end < elem.Time.TotalMilliseconds)
                        //    _end = elem.Time.TotalMilliseconds;
                        if (!_frame.ContainsKey(elem.Time))
                            _frame.Add(elem.Time, new Dictionary<KeyValuePair<int, string>, CTransform>());

                        Vector3 _scale = Vector3.Zero;
                        Quaternion _rot = Quaternion.Identity;
                        Vector3 _pos = Vector3.Zero;

                        if (!elem.Transform.Decompose(out _scale, out _rot, out _pos))
                            throw new Exception(string.Format("Pose matrix of {0} cound't be decomposed", anim.Key));
                        CTransform ct = new CTransform(elem.Transform);
                        KeyValuePair<int, string> _key = new KeyValuePair<int, string>(nodeIndex, anim.Key);
                        if (!RemoveUselessKey || CTransform.Diff(ct, node.AbsolutePose))
                        {
                            //if (!_frame[elem.Time].ContainsKey(_key))
                            _frame[elem.Time].Add(_key, ct);
                            if (!_nodeAnimated.Contains(node))
                                _nodeAnimated.Add(node);
                        }
                        else// if (!_frame[elem.Time].ContainsKey(_key))
                            _frame[elem.Time].Add(new KeyValuePair<int, string>(nodeIndex, anim.Key), null);
                    }
                }

                CompleteAnimation(_frame);

                m_animation.Add((from keyframe in _frame
                                 let pose = (from e in keyframe.Value
                                             orderby e.Key.Key
                                             select e.Value)
                                 orderby keyframe.Key
                                 select new CKeyFrame(keyframe.Key).AddPose(pose)));

                m_animation.Finalize(_nodeAnimated);
                m_animContent.AddAnimation(m_animation);
                //m_animationBank.AddAnimation(_ani);
                //_boneAnimated.Clear();
            }


        }

        /// <summary>
        /// Method than complet animation information by null transfom to uniforme data content
        /// </summary>
        /// <param name="_frame">frame information</param>
        private void CompleteAnimation(Dictionary<TimeSpan, Dictionary<KeyValuePair<int, string>, CTransform>> _frame)
        {
            int size = 0;
            foreach (var elem in _frame)
            {
                if (size < elem.Value.Count)
                    size = elem.Value.Count;
            }
            foreach (var elem in _frame)
            {
                Dictionary<KeyValuePair<int, string>, CTransform> _trs = elem.Value;
                if (elem.Value.Count < size)
                {
                    for (int i = 0; i < size; ++i)
                    {
                        if ((from el in _trs where el.Key.Key == i select el).Count() == 0)
                            _trs.Add(new KeyValuePair<int, string>(i, null), null);
                    }
                }
            }
        }

        #endregion
    }
}
